﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using NFit.Core.Interfaces;
using System.Configuration;
using log4net;

namespace NFit.Core.Config.Xml
{
    /// <summary>
    /// Configurator for .Net configuration file
    /// </summary>
    public class XmlConfigurator : INFitConfigurator
    {
        private static ILog Logger = log4net.LogManager.GetLogger(typeof(XmlConfigurator));

        private IList<ISpecificationDataSource> _datasources;
        /// <summary>
        /// The list of data sources
        /// </summary>
        public IList<ISpecificationDataSource> Datasources
        {
            get { return _datasources; }
        }

        private IList<Assembly> _fixtureAssemblies;

        /// <summary>
        /// The list of assemblies containing fixtures
        /// </summary>
        public IList<Assembly> FixtureAssemblies
        {
            get { return _fixtureAssemblies; }
        }

        private IList<IReportWriter> _reportWriters;

        private NFitLoaderSection configSection;


        private bool IsLoaded
        {
            get
            {
                return _datasources != null && _fixtureAssemblies != null;
            }
        }

        /// <summary>
        /// Constructor
        /// </summary>
        public XmlConfigurator()
        {
            configSection = ConfigurationManager.GetSection("nfit") as NFitLoaderSection;
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="fileName"></param>
        public XmlConfigurator(string fileName)
        {
            Logger.Debug("Opening configuration from " + fileName);
            ExeConfigurationFileMap map = new ExeConfigurationFileMap();
            map.ExeConfigFilename = fileName;
            Configuration cfg = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
            configSection = cfg.GetSection("nfit") as NFitLoaderSection;            
        }

        private void LoadConfiguration(NFitLoaderSection section)
        {
            _datasources = new List<ISpecificationDataSource>();
            _fixtureAssemblies = new List<Assembly>();
            _reportWriters = new List<IReportWriter>();

            
            if (section == null)
            {
                Logger.Debug("config section not found");
            }
            else
            {
                Logger.Debug("nb data sources " + section.Datasources.Count);
                foreach (DatasourceElement elt in section.Datasources)
                {
                    AddDatasource(elt);
                }
                Logger.Debug("nb fixture assemblies " + section.FixtureCatalog.Count);
                foreach (FixtureAssemblyElement elt in section.FixtureCatalog)
                {
                    AddFixtureAssembly(elt);
                }

                Logger.Debug("nb writers " + section.ReportWriters.Count);
                foreach (ReportWriterElement elt in section.ReportWriters)
                {
                    AddWriter(elt);
                }

            }
        }

        private void AddWriter(ReportWriterElement writerElt)
        {
            IReportWriter writer = GetInstanceOf<IReportWriter>(writerElt.Type, writerElt.Arguments);
            if (writer != null)
            {
                writer.Writer = GetInstanceOf<ISpecificationWriter>(writerElt.Writer.Type,
                                                                    writerElt.Writer.Arguments);
                _reportWriters.Add(writer);
            }
            
        }

        private void AddDatasource(DatasourceElement sourceElt)
        {
            Logger.Debug("reading data source " + sourceElt.Name);

            ISpecificationReader reader;
            ISpecificationDataSource source;

            reader = GetInstanceOf<ISpecificationReader>(sourceElt.Reader.Type, sourceElt.Reader.Arguments);
            source = GetInstanceOf<ISpecificationDataSource>(sourceElt.Type, sourceElt.Arguments);
            source.Reader = reader;
            source.Name = sourceElt.Name;

            if (source == null)
                return;

            Logger.Debug("source is " + source.GetType().FullName);
            if (source.Reader != null)
                Logger.Debug("reader is " + source.Reader.GetType().FullName);

            Datasources.Add(source);
        }

        private void AddFixtureAssembly(FixtureAssemblyElement assElt)
        {
            Assembly ass = Assembly.Load(assElt.Name);
            if (ass != null)
            {
                FixtureAssemblies.Add(ass);
            }
        }

        private void LoadIfNotLoaded()
        {
            if (!IsLoaded)
                LoadConfiguration(configSection);
        }

        /// <summary>
        /// Get an enumeration of data sources
        /// </summary>
        /// <returns></returns>
        public IEnumerable<ISpecificationDataSource> GetDatasources()
        {
            LoadIfNotLoaded();
            return Datasources;
        }

        /// <summary>
        /// Get an enumeration of assemblies containing fixtures
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Assembly> GetFixtureAssemblies()
        {
            LoadIfNotLoaded();
            return FixtureAssemblies;
        }

        /// <summary>
        /// Get the report writer
        /// </summary>
        /// <returns></returns>
        public IEnumerable<IReportWriter> GetWriters()
        {
            return _reportWriters;
        }

        /// <summary>
        /// Create and fill an instance of the type provided
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="typeName"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public static T GetInstanceOf<T>(string typeName, KeyValueConfigurationCollection args)
        {
            T inst = default(T);

            if (!string.IsNullOrEmpty(typeName))
            {
                Type eltType = Type.GetType(typeName);
                if (eltType != null)
                {
                    inst = (T)eltType.Assembly.CreateInstance(eltType.FullName);
                    if (args != null && args.Count > 0)
                    {
                        string[] propNames = args.AllKeys;
                        foreach (string s in propNames)
                        {
                            PropertyInfo propNfo = eltType.GetProperty(s);
                            if (propNfo != null)
                            {
                                propNfo.SetValue(inst, Convert.ChangeType(args[s].Value, propNfo.PropertyType),
                                                 null);
                            }
                            else
                            {
                                throw new KeyNotFoundException(
                                    string.Format("The source object {0} does not have a property called {1}",
                                                  eltType.FullName, s));
                            }
                        }
                    }
                }
            }
            return inst;
        }
    }
}
